#include "table.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define HS_SIZE 127
#define SYM_STK_INIT_CAP 512
#define BLK_STK_INIT_CAP 16

typedef struct st_binding *binding;

struct st_binding {
    symbol sym;
    void *value;
    int link;
};

struct st_table {
    binding sym_stk;
    size_t sym_stk_cap;
    size_t top_sym_stk;

    int *blk_stk;
    size_t blk_stk_cap;
    size_t top_blk_stk;

    int hs[HS_SIZE];
};

table cre_table() {
    table t = malloc(sizeof(*t));
    memset(t, 0, sizeof(*t));
    t->sym_stk_cap = SYM_STK_INIT_CAP;
    t->top_sym_stk = 0;
    t->sym_stk = calloc(t->sym_stk_cap, sizeof(struct st_binding));

    t->blk_stk_cap = BLK_STK_INIT_CAP;
    t->top_blk_stk = 0;
    t->blk_stk = calloc(t->blk_stk_cap, sizeof(int));

    for (int i = 0; i < HS_SIZE; ++i)
        t->hs[i] = -1;
    return t;
}

void rel_table(table t) {
    free(t->sym_stk);
    free(t->blk_stk);
    free(t);
}

void table_enter(table t, symbol sym, void *v) {
    if (t->top_sym_stk >= t->sym_stk_cap) {
        t->sym_stk_cap += 128;
        t->sym_stk = reallocarray(t->sym_stk, t->sym_stk_cap, sizeof(struct st_binding));
    }
    int pos = t->top_sym_stk++;             // push sym_stk
    size_t hash = (size_t)sym % HS_SIZE;
    int index = t->hs[hash];
    t->sym_stk[pos].sym = sym;
    t->sym_stk[pos].value = v;
    t->sym_stk[pos].link = index;
    t->hs[hash] = pos;                      // update hash table
}

void *table_look(table t, symbol sym) {
    size_t hash = (size_t)sym % HS_SIZE;
    int index = t->hs[hash];
    if (index < 0 || index >= t->top_sym_stk)
        return NULL;

    for (; 0 <= index && index < t->top_sym_stk; index = t->sym_stk[index].link)
        if (t->sym_stk[index].sym == sym)
            return t->sym_stk[index].value;

    return NULL;
}

void begin_scope(table t) {
    if (t->top_blk_stk >= t->blk_stk_cap) {
        t->blk_stk_cap += 16;
        t->blk_stk = reallocarray(t->blk_stk, t->blk_stk_cap, sizeof(int));
    }
    t->blk_stk[t->top_blk_stk++] = t->top_sym_stk;
}

void end_scope(table t) {
    assert(t->top_blk_stk > 0);
    int flag = t->blk_stk[t->top_blk_stk-1];
    for (int i = t->top_sym_stk-1; i >= flag; --i) {
        binding it = t->sym_stk + i;
        symbol sym = it->sym;
        size_t hash = (size_t)sym % HS_SIZE;
        t->hs[hash] = t->sym_stk[t->hs[hash]].link; // update hash table
    }
    t->top_sym_stk = t->blk_stk[--t->top_blk_stk];  // pop sym_stk, pop blk_stk
}